"""
Main API module. Contains Node and RootNode classes
which are geric representation of objects at the
repository level.
"""
import re
import posixpath as path
import mimetypes
import warnings
import logging
import pprint

from vcbrowser.enumerations import RepositoryType, NodeKind
from vcbrowser import settings

try:
    import pygments
    USE_PYGMENTS = True
except ImportError:
    USE_PYGMENTS = False

class NodeError(Exception):
    pass

class NodeFileTooBigError(NodeError):
    pass

def RequestedNode(url, kind):
    """
    Dispatch request for node and returns FileNode or DirNode.
    """
    if kind is NodeKind.FILE:
        return FileNode(url, content='')
    elif kind is NodeKind.DIR:
        return DirNode(url)

class NodeInfo(object):
    """
    Storages node info (size, revision, author, created_at).
    """
    _available_attrs = ['size', 'revision', 'author', 'date', 'message', 'mime_type']
    def __init__(self, **kwargs):
        kwargs.setdefault('mime_type', 'unknown/unknown')
        for attr in NodeInfo._available_attrs:
            kwargs.setdefault(attr, None)
        self.__dict__.update(kwargs)

    def __str__(self):
        return '<NodeInfo %s>' % pprint.pformat(self.__dict__)

class Node(object):
    """
    Simplest class representing file or directory on 
    repository platform. This is abstract object.
    Should work for url with or without trailing slash.
    Node should store information (info as dict) like:
        size as int
        rev_number as int
        created_at as datetime
        mime_type
        ... something more for sure
    """

    def __init__(self, url, kind):
        self.url = url
        self.kind = kind
        self.info = NodeInfo()
        
        self.dirs, self.files = [], []
        # Setup additional info
        self._configure()

    def __cmp__(self, other):
        """
        Comparator using name of the node.
        """
        return cmp(self.name, other.name)

    def __repr__(self):
        return '<Node "%r">' % self.url

    def __unicode__(self):
        return unicode(self.name)

    def _configure(self):
        self.repo_url = self._get_repo_url()
        self.rel_url = self._get_rel_url()
        self.name = self._get_name()
        self.node_up_url = self._get_node_up_url()
    
    @staticmethod
    def get_repo_url(url):
        repo_url = '/'.join(url.split('/')[:3])
        repo_url = repo_url.rstrip('/')
        return repo_url

    def _get_repo_url(self):
        #repo_url = '/'.join(self.url.split('/')[:3])
        #repo_url = repo_url.rstrip('/')
        repo_url = Node.get_repo_url(self.url)
        return repo_url

    @staticmethod
    def get_rel_url(url):
        """
        Returns relative fragment of the given url.
        """
        rel_url = '/' + '/'.join(url.split('/')[3:])
        rel_url = rel_url.rstrip('/')
        if not rel_url:
            rel_url = '/'
        return rel_url

    def _get_rel_url(self):
        rel_url = Node.get_rel_url(self.url)
        return rel_url
    
    @staticmethod
    def join_url(url, *urls):
        joined_url = path.join(url, *urls)
        return joined_url

    @staticmethod
    def get_name(url):
        """
        Returns name of the node so if its url
        then only last part is returned.
        """
        return url.split('/')[-1]

    def _get_name(self):
        '''
        repo_url = self._get_repo_url()
        if self.url == repo_url or self.url == repo_url + '/':
            return ''
        name = path.split(self.url.rstrip('/'))[1]
        '''
        name = self.url.split('/')[-1]
        return name
    
    def _get_node_up_url(self):
        if self.name == '':
            return None
        node_up_url = '/'.join(self.url.split('/')[:-1])
        return node_up_url
    
    def is_file(self):
        return self.kind == NodeKind.FILE
    
    def is_dir(self):
        return self.kind == NodeKind.DIR
    
    def get_mimetype(self, content):
        if self.is_dir():
            raise NodeError("Directories do not define mimetype!")
        if self.info.mime_type:
            return self.info.mime_type
        mtype = mimetypes.guess_type(self.name)[0]
        if mtype is None:
            try:
                content.decode('utf-8')
                mtype = 'text/plain'
            except UnicodeDecodeError:
                print "Cannot decode!"
                mtype = 'application/octet-stream'
        self.info.mime_type = mtype
        return mtype
    

class FileNode(Node):
    """
    FileNode stores content of the file.
    """
    
    def __init__(self, url, content):
        super(FileNode, self).__init__(url, NodeKind.FILE)
        self.content = content

    def __repr__(self):
        return '<FileNode %r>' % self.url
    
    def get_content(self):
        return self._content

    def set_content(self, content):
        """
        Only content size is checked here so be careful implementing
        backend methods!
        """
        if not self.is_file():
            raise NodeError("Is not a file!")
        if content and len(content) > settings.MAX_FILE_SIZE:
            raise NodeFileTooBigError("File is too big.")
        self._content = content
    
    content = property(get_content, set_content)

    def get_pygmentized(self, formatter_alias='html', **options):
        """
        If pygments are available they will be used to highlight
        content of the node.
        Lexer is guessed basing on mimetype if available. Otherwise
        will try to decode file's content to ascii. Could also use
        recipe 4.18 from "Python Cookbook Second Ed." but it is
        possible that backend will get proper mime-type.
        """
        if not USE_PYGMENTS:
            self._pygmented_content = '<pre>' + self.content + '</pre>'
        if USE_PYGMENTS and not hasattr(self, '_pygmented_content'):
            from pygments import highlight
            from pygments import lexers
            from pygments.formatters import HtmlFormatter
            from pygments.formatters import get_formatter_by_name
            
            mtype = self.get_mimetype(self._content)
            
            try:
                lexer = lexers.get_lexer_for_filename(self.name, stripall=True)
                if isinstance(lexer, lexers.HtmlLexer):
                    lexer = lexers.HtmlDjangoLexer()
            except lexers.ClassNotFound:
                # Not common type. Try to find out basing on mime-type
                try:
                    self._content.decode('utf-8')
                except UnicodeDecodeError:
                    self._content = '<Binary data>'
                if self._content.lstrip().startswith('<?xml'):
                    lexer = lexers.XmlLexer()
                else:
                    lexer = lexers.TextLexer()
            
            formatter_cls = get_formatter_by_name(formatter_alias)
            if isinstance(formatter_cls, HtmlFormatter):
                formatter = HtmlFormatter(linenos=True, cssclass="code-highlight",
                                      lineanchors='foo', anchorlinenos=True)
            self._pygmented_content = highlight(self.content, lexer, formatter)
        return self._pygmented_content
    

class DirNode(Node):
    """
    DirNode stores list of files and directories within this node.
    """

    def __init__(self, url, nodes):
        super(DirNode, self).__init__(url, NodeKind.DIR)
        self.nodes = nodes
    
    def __repr__(self):
        return '<DirNode %r>' % self.url

    def get_nodes(self):
        """
        Returns dirs + files lists of nodes of the current root node.
        """
        return self._nodes
    
    def set_nodes(self, nodes):
        """
        Backends should call this method if root is a dir.
        """
        self.files = [node for node in nodes if node.is_file()]
        self.dirs = [node for node in nodes if node.is_dir()]

        if not self.is_dir():
            raise NodeError("Is not a dir!")
        if self.name != '':
            # Need to add 'level-up' node if not at repository's root
            node_up = Node(url=self.node_up_url, kind=NodeKind.DIR)
            node_up.name = '..'
            nodes.insert(0, node_up)
        
        self._nodes = nodes

    nodes = property(get_nodes, set_nodes)

